/**
 * Copyright (c) 2013 committers of YAKINDU and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * Contributors:
 * 	committers of YAKINDU - initial API and implementation
 * 
 */
package org.yakindu.sct.refactoring.refactor.impl;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.yakindu.base.types.Expression;
import org.yakindu.sct.model.sgraph.Effect;
import org.yakindu.sct.model.sgraph.Scope;
import org.yakindu.sct.model.sgraph.State;
import org.yakindu.sct.model.sgraph.Transition;
import org.yakindu.sct.model.stext.stext.ExitEvent;
import org.yakindu.sct.model.stext.stext.LocalReaction;
import org.yakindu.sct.model.stext.stext.ReactionEffect;
import org.yakindu.sct.model.stext.stext.ReactionTrigger;
import org.yakindu.sct.model.stext.stext.StextFactory;
import org.yakindu.sct.refactoring.refactor.AbstractRefactoring;

/**
 * This refactoring moves actions of outgoing transitions to the exit block of a state. Actions can only be moved if 
 * they are used at all outgoing transitions in the same order (checked from front to back). 
 * <br><br>
 * Context:
 * <ul>
 * <li>Exactly one state.</li>
 * </ul>
 * Preconditions:
 * <ul>
 * <li>At least one action is foldable.</li>
 * <li>No outgoing transition leaves a composite state which has exit actions.</li>
 * </ul>
 * @author thomas kutz - Initial contribution and API
 * 
 */
public class FoldOutgoingActionsRefactoring extends AbstractRefactoring<State> {

	@Override
	protected void internalExecute() {
		EList<Transition> outgoingTransitions = getContextObject()
				.getOutgoingTransitions();
		List<Expression> actionsToFold = getFoldableActions(outgoingTransitions);
		addActionsToExitReaction(actionsToFold);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isExecutable() {
		return super.isExecutable()
				&& atLeastOneFoldableAction()
				&& noOutgoingTransitionLeavesCompositeWithExitActions();
	}

	private boolean noOutgoingTransitionLeavesCompositeWithExitActions() {
		return !helper.oneOutgoingTransitionLeavesCompositeWithExitActions(getContextObject());
	}
	
	private boolean atLeastOneFoldableAction() {
		EList<Transition> transitions = getContextObject().getOutgoingTransitions();
		return getFirstFoldableAction(helper.getAllActions(transitions), 0) != null;
	}

	private List<Expression> getFoldableActions(EList<Transition> transitions) {

		List<Expression> foldableActions = new ArrayList<Expression>();

		Expression firstFoldableAction;
		int index = 0;
		while ((firstFoldableAction = getFirstFoldableAction(
				helper.getAllActions(transitions), index)) != null) {
			foldableActions.add(firstFoldableAction);
			index++;
		}
		removeFirstActions(transitions, index);

		return foldableActions;
	}

	private void addActionsToExitReaction(final List<Expression> actionsToAdd) {

		if (actionsToAdd.isEmpty()) {
			return;
		}

		EList<Expression> actionsOriginal = helper
				.getFirstExitActions(getContextObject());

		if (actionsOriginal == null) {
			actionsOriginal = createExitBlock(actionsToAdd);
		} else {
			actionsOriginal.addAll(actionsToAdd);
		}

	}

	private EList<Expression> createExitBlock(List<Expression> actionsToAdd) {
		EList<Expression> actionsOriginal;
		LocalReaction newLocalReaction = StextFactory.eINSTANCE
				.createLocalReaction();
		ReactionTrigger newReactionTrigger = StextFactory.eINSTANCE
				.createReactionTrigger();
		ExitEvent exitEvent = StextFactory.eINSTANCE.createExitEvent();
		ReactionEffect newReactionEffect = StextFactory.eINSTANCE
				.createReactionEffect();

		newLocalReaction.setTrigger(newReactionTrigger);
		newReactionTrigger.getTriggers().add(exitEvent);
		
		newReactionEffect.getActions().addAll(actionsToAdd);
		newLocalReaction.setEffect(newReactionEffect);

		Scope scope = getContextObject().getScopes().get(0);
		scope.getReactions().add(newLocalReaction);

		actionsOriginal = newReactionEffect.getActions();
		return actionsOriginal;
	}

	private void removeFirstActions(EList<Transition> transitions, int number) {

		for (Transition transition : transitions) {
			List<Expression> actionsToRemove = getFirstActions(transition,
					number);
			Effect effect = transition.getEffect();
			if (effect instanceof ReactionEffect && actionsToRemove.size() == ((ReactionEffect)effect).getActions().size()) {
				// we need to remove all actions, so just remove the effect recursively which avoids serializer exceptions
				EcoreUtil.delete(effect, true);
			} else {
				for (Expression action : actionsToRemove) {
					EcoreUtil.delete(action);
				}
			}
		}
	}

	private List<Expression> getFirstActions(Transition transition, int number) {
		List<Expression> firstActions = new ArrayList<Expression>();
		Effect effect = transition.getEffect();
		if (effect instanceof ReactionEffect) {
			ReactionEffect reactionEffect = (ReactionEffect) effect;
			List<Expression> actions = reactionEffect.getActions();
			for (int i = 0; i < number; i++) {
				firstActions.add(actions.get(i));
			}
		}
		return firstActions;
	}

	private Expression getFirstFoldableAction(
			List<List<Expression>> allActions, int index) {
		Expression actionToCheck = null;
		for (List<Expression> actionList : allActions) {
			if (index >= actionList.size()) {
				return null;
			}
			Expression firstAction = actionList.get(index);
			if (actionToCheck == null) {
				actionToCheck = firstAction;
			} else if (!EcoreUtil.equals(actionToCheck, firstAction)) {
				return null;
			}
		}
		return actionToCheck;
	}

	@Override
	protected String getCommandLabel() {
		return "Fold Outgoing Actions";
	}

}
